#include "lv_romfs.h"

#if LV_USE_FILESYSTEM && USE_LV_ROM_FS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#if USE_LV_PCFS
#include <SDL2/SDL.h>
#endif
#include "lv_fs.h"
#include "lv_drivers/common/utils.h"
#include "../../board.h"
#include "../../hal/hal.h"
#include "lv_pcfs.h"

#define ROMFS_DEBUG_EN 0
#define ROMFS_DEBUG_TIME_EN 0
#define ROMFS_DEBUG_CACHE_EN 0

#define ROMFH_TYPE 7
#define ROMFH_HRD 0
#define ROMFH_DIR 1
#define ROMFH_REG 2
#define ROMFH_SYM 3
#define ROMFH_BLK 4
#define ROMFH_CHR 5
#define ROMFH_SCK 6
#define ROMFH_FIF 7
#define ROMFH_EXEC 8

/* Alignment */

#define ROMFH_SIZE 16
#define ROMFH_PAD (ROMFH_SIZE-1)
#define ROMFH_MASK (~ROMFH_PAD)

/*Decrement life with this value in every open*/
#define LV_IMG_CACHE_AGING 1

/*Boost life by this factor (multiply time_to_open with this value)*/
#define LV_IMG_CACHE_LIFE_GAIN 1

/*Don't let life to be greater than this limit because it would require a lot of time to
 * "die" from very high values */
#define LV_IMG_CACHE_LIFE_LIMIT 1000

/* On-disk "super block" */

struct romfs_super_block {
    __be32 word0;
    __be32 word1;
    __be32 size;
    __be32 checksum;
    char name[0]; /* volume name */
};

/* On disk inode */

struct romfs_inode {
    __be32 next; /* low 4 bits see ROMFH_ */
    __be32 spec;
    __be32 size;
    __be32 checksum;
    char name[0];
};

struct file_desc {
    struct romfs_inode *h;
    int rd_pos;
    int open_cnt;
};

#define FD_BUF_SIZE 32
#define FD_IDX_MASK 0xFFFF

/*romfs file name should less than 128 bytes. eg: "R:/png/setting/icon_setting_dail_format_switch.png" */
#define ROMFS_MAX_CHAR_NAME 128

struct buf_desc {
    int id;
    struct file_desc fd_buf[FD_BUF_SIZE];
    int size;
    int used;
    struct buf_desc *next;
};

static struct romfs_super_block *grsb;
static bool romfs_is_xip = true;
static struct romfs_inode *romfs_inode_first_head = NULL;

typedef struct {
    struct romfs_inode *h;
    char fname[128];
    /** How much time did it take to open the image. [ms]
     *  If not set `lv_img_cache` will measure and set the time to open*/
    uint32_t time_to_open;
    /** Count the cache entries's life. Add `time_tio_open` to `life` when the entry is used.
     * Decrement all lifes by one every in every ::lv_img_cache_open.
     * If life == 0 the entry can be reused */
    int life;
} file_inode_desc_t;

/* eg: status and setting.c: alway refresh icon_signal_4 and icon_battery_4 frequently.
 * need cache them for lv_romfs_open invoked by lv_img_decoder_get_info().
 */
#ifndef FILE_INODE_BUF_SIZE
#define FILE_INODE_BUF_SIZE 4
#endif
static file_inode_desc_t file_inode_desc[FILE_INODE_BUF_SIZE];

static struct buf_desc *buf_desc_head;
static int next_buf_id;
static int romfs_inited = 0;
static int use_lock = 0;
static int drv_added;

#ifndef USE_LV_PCFS
#define PARTITION_NAME "resource"

typedef struct {
    struct romfs_inode *h; /* vaddr of inode*/
    struct romfs_inode *inode; /*malloc space: size of inode + ROMFS_MAX_CHAR_NAME*/
    int inode_size;
} inode_desc_t;

static char romfs_inode[sizeof(struct romfs_inode) + 128];
/* cache one inode with charname[ROMFS_MAX_CHAR_NAME]'s data for internel flash->read */
static inode_desc_t inode_desc;
#endif

static uint8_t romfs_mutex __attribute((unused));

#ifndef USE_LV_PCFS
static bool partition_is_internal_flash(const char *pname) {
    return is_partition_internal_flash(pname);
    //fake
    //printf("fake: partition_is_internal_flash: return true\n");
    //return true;
}

/*
 * craneM no partition_ops_read in sdk, so use weak function.
 * craneG has partition_ops_read in sdk, use function from partiton_ops.c
 *
 * already in stable branch, product/crane_modem/softutil/partition_raw/src/partition_ops.c
 * internel flash or external flash common function,
 * internal flash (spi flash read), external flash(qspi nor flash xip read)
 */
int fake_partition_ops_read(const char * pname, uint32_t offset , uint32_t length , char * buf_addr)
{
    (void)pname;
    (void)offset;
    (void)length;
    (void)buf_addr;

    _ptentry * resource_begin_Entry= ptable_find_entry(pname);

    //only fake external flash XIP read.
    memcpy(buf_addr, (char *)(resource_begin_Entry->vstart + offset), length);

    /* simulator partition_ops.c, always return 0 */
    return 0;
}

//internel or external common
static int partition_read(const char *vaddr, uint32_t len, char *buf)
{
    int offset = vaddr - (char *)grsb;
    if(offset < 0) {
        printf("[%s] wrong vaddr.\n", __func__);
        return -1;
    }

    _ptentry * resource_entry= ptable_find_entry(PARTITION_NAME);
    if(offset + len > resource_entry->size){
        printf("[%s] offset=0x%x, len=0x%x, out of partition range[0x%x].\n", __func__, offset, (unsigned int)len, resource_entry->size);
        return -1;
    }

    /*lv_freetype.c FT2_read has case, len = 0, buf=NULL */
    if(len == 0) {
        return 0;
    }

    if(len > 0 && buf == NULL) {
        printf("[%s] failed. buf is NULL!\n", __func__);
        return -1;
    }

    //if(fake_partition_ops_read(PARTITION_NAME, offset, len, buf)) {
    if(partition_ops_read(PARTITION_NAME, offset, len, buf)) {
        printf("[%s] failed.\n", __func__);
        return -1;
    }
    return len;
}
#endif

static void *rd_super_block(struct romfs_super_block *h)
{
    if(!romfs_is_xip) {
#ifndef USE_LV_PCFS
        char *inode = (char *)inode_desc.inode;
        int rd_size = partition_read((char *)h, inode_desc.inode_size, inode);
        if(rd_size < 0 || rd_size < sizeof(struct romfs_super_block)) {
            printf("[%s] partition_read failed\n", __func__);
            return NULL;
        }
        /* name of volume should be finished with \0, len of name is unknown, use ROMFS_MAX_CHAR_NAME space to read name */
        inode[rd_size-1]='\0';
        return inode;
#endif
    }

    //PCFS or external use XIP read
    return h;
}

static void *rd_inode(struct romfs_inode *h)
{
    if(!romfs_is_xip) {
#ifndef USE_LV_PCFS
        char *inode = (char *)inode_desc.inode;

        if(inode_desc.h == h) {
            //printf("[%s]: find cached h=0x%x\n", __func__, (unsigned int)h);
            return inode;
        } else {
            int rd_size = partition_read((const char *)h, inode_desc.inode_size, inode);
            /*name len is not sure how long, so not to check rd_size must be same as inode_desc.size */
            if(rd_size < 0 || rd_size < sizeof(struct romfs_inode)) {
                printf("[%s] partition_read failed\n", __func__);
                return NULL;
            }
            /* name of inode should be finished with \0, len of name is unknown, use ROMFS_MAX_CHAR_NAME space to read name */
            inode[rd_size-1]='\0';

            inode_desc.h = h;
            //printf("[%s]: cached new h=0x%x\n", __func__, (unsigned int)h);
        }
        return inode;
#endif
    }

    //PCFS or external use XIP read
    return h;
}

static uint32_t romfs_get_inode_next(struct romfs_inode *h)
{
    h = (struct romfs_inode *)rd_inode(h);
    if(!h) return 0;

    uint32_t next = (uint32_t)be32_to_cpu(h->next);
    return next;
}

static uint32_t romfs_get_inode_spec(struct romfs_inode *h)
{
    h = (struct romfs_inode *)rd_inode(h);
    if(!h) return 0;

    uint32_t spec = (uint32_t)be32_to_cpu(h->spec);
    return spec;
}

static uint32_t romfs_get_inode_size(struct romfs_inode *h)
{
    h = (struct romfs_inode *)rd_inode(h);
    if(!h) return 0;

    uint32_t size = (uint32_t)be32_to_cpu(h->size);
    return size;
}

static int romfs_strcmp(const char *name, struct romfs_inode *h)
{
    h = (struct romfs_inode *)rd_inode(h);
    if(!h) return 0;

    int ret = strcmp(name, h->name);

#if ROMFS_DEBUG_EN
    if(ret == 0) printf("[%s] target name=%s,  h->name=%s.\n", __func__, name, h->name);
#endif
    return ret;
}


static void *align_to_16_byte(void *addr)
{
    intptr_t t = (intptr_t)addr;

    if (t & 0xF) {
        t = (t + 16) & ~0xF;
    }

    return (void *)t;
}

static uint8_t *get_file_data(struct romfs_inode *h)
{
    char *data;
    int data_offset = 0;
    struct romfs_inode * tmp_h = (struct romfs_inode *)rd_inode(h);
    if(!tmp_h) {
        printf("[%s]: failed!\n", __func__);
        return NULL;
    }
    data_offset = strlen(tmp_h->name) + 1;

    //vaddr: eg: internal 0x90000020(can not xip read). external 0x80000020(can xip read)
    data = h->name + data_offset;
    return align_to_16_byte((void *)data);
}

static struct romfs_inode *get_first_head(void)
{
    if(romfs_inode_first_head) return romfs_inode_first_head;

    void *addr = NULL;
    struct romfs_super_block *rsb = grsb;

    if (grsb) {
        rsb = (struct romfs_super_block *)rd_super_block(rsb);
        if(!rsb) return NULL;

        //vaddr: eg: internal 0x90000020(can not xip read). external 0x80000020(can xip read)
        char *data = (char *)grsb + (rsb->name + strlen(rsb->name) + 1 - (char *)rsb);
        addr = align_to_16_byte((void *)data);
    }

#if ROMFS_DEBUG_EN
    printf("[%s] grsb=0x%x, addr=0x%x\n", __func__, (unsigned)grsb, (unsigned)addr);
#endif

    romfs_inode_first_head = (struct romfs_inode *)addr;

    return (struct romfs_inode *)addr;
}

static struct romfs_inode *get_first_head_in_dir(struct romfs_inode *dir)
{
    intptr_t base;
    uint32_t offset;

    base = (intptr_t)grsb;

    offset = (intptr_t)romfs_get_inode_spec(dir) & ~0xF;

    return (struct romfs_inode *)(base + offset);
}

static struct romfs_inode *get_next_head(struct romfs_inode *h)
{
    intptr_t base;
    uint32_t offset;

    base = (intptr_t)grsb;

    offset = romfs_get_inode_next(h) & ~0xF;
    if (offset == 0)
        return NULL;
    else
        return (struct romfs_inode *)(base + offset);
}


static int is_dir(struct romfs_inode *h)
{
    int ret = (romfs_get_inode_next(h) & ROMFH_DIR);
    return ret;
}

static int is_usr_dir(struct romfs_inode *h)
{
    if (is_dir(h) && ((romfs_strcmp(".", h) != 0) && (romfs_strcmp("..", h) != 0))) {
        return 1;
    }

    return 0;
}

static int is_sys_file(struct romfs_inode *h)
{
    if ((romfs_strcmp(".", h) == 0) || (romfs_strcmp("..", h) == 0)) {
        return 1;
    }

    return 0;
}

static struct romfs_inode *get_root_dir(void)
{
    return get_first_head();
}

static struct romfs_inode *_find_dir(struct romfs_inode *parent, const char *dir_name)
{
    struct romfs_inode *h;

    h = get_first_head_in_dir(parent);
    while (h) {
        if (is_usr_dir(h)) {
            if (romfs_strcmp(dir_name, h) == 0) {
                return h;
            }
        }
        h = get_next_head(h);
    }

    return h;
}

static struct romfs_inode *find_dir(struct romfs_inode *dir, const char *dir_name)
{
    return _find_dir(dir, dir_name);
}

static struct romfs_inode *get_first_dir(struct romfs_inode *dir, const char *file_name, const char **const file_name_remain)
{
    char dir_buf[32];
    char *end;
    int len;

    if (file_name[0] == '/') {
        *file_name_remain = file_name + 1;
        return get_root_dir();
    } else {
        end = strstr(file_name, "/");
        if (!end) {
            return NULL;
        } else {
            len = end - file_name;
            memcpy(dir_buf, file_name, len);
            dir_buf[len] = '\0';
            *file_name_remain = end + 1;
            return find_dir(dir, dir_buf);
        }
    }
}

static struct romfs_inode *_find_file_nosub(struct romfs_inode *dir, const char *file_name)
{
    struct romfs_inode *h;

    h = get_first_head_in_dir(dir);
    while (h) {
        if ((is_dir(h) == 0) && (is_sys_file(h) == 0) && (romfs_strcmp(file_name, h) == 0)) {
            break;
        }
        h = get_next_head(h);
    }

    return h;
}

static struct romfs_inode *_find_file(struct romfs_inode *dir, const char *file_name, int subdir)
{
    struct romfs_inode *next_dir, *h;
    const char *file_name_remain;

    next_dir = get_first_dir(dir, file_name, &file_name_remain);

    if (next_dir) {
        h = _find_file(next_dir, file_name_remain, 1);
    } else {
        h = _find_file_nosub(dir, file_name);
    }

    return h;
}

static struct romfs_inode *find_file(const char *file_name, int subdir)
{
    struct romfs_inode *root_dir;
    const char *p = file_name;

    if (p[0] == '/') {
        p++;
    }

    root_dir = get_root_dir();
    if (root_dir) {
        return _find_file(root_dir, p, subdir);
    } else {
        return NULL;
    }
}

static int fd_is_invalid(uint32_t fd)
{
    return ((fd & FD_IDX_MASK) >= FD_BUF_SIZE)?1:0;
}

static int parse_fd(uint32_t fd, int *buf_id, int *idx)
{
    if (fd_is_invalid(fd)) {
        printf("parse_fd: bad fd 0x%x\n", (unsigned)fd);
        return -1;
    }

    *buf_id = (fd >> 16) & 0xFFFF;
    *idx = fd & 0xFFFF;

    return 0;
}

static int fd_to_desc(uint32_t fd, struct buf_desc **buf_d, struct file_desc **file_d)
{
    struct buf_desc *p = buf_desc_head;
    int buf_id, idx;

    if (parse_fd(fd, &buf_id, &idx) < 0) {
        return -1;
    }

    while (p) {
        if (p->id == buf_id) {
            break;
        }
        p = p->next;
    }

    if (!p) {
        return -1;
    }

    if (buf_d) {
        *buf_d = p;
    }
    if (file_d) {
        *file_d = &p->fd_buf[idx];
    }

    return 0;
}

static uint32_t mk_fd(int buf_id, int idx)
{
    return (uint32_t)(((buf_id & 0xFFFF) << 16) | (idx & 0xFFFF));
}

static int get_file_fd(struct romfs_inode *h, uint32_t *fd)
{
    struct buf_desc *p = buf_desc_head;
    int i;

    while (p) {
        for (i = 0; i < FD_BUF_SIZE; i++) {
            if ((h == p->fd_buf[i].h) && (p->fd_buf[i].open_cnt > 0)) {
                *fd = mk_fd(p->id, i);
                return 0;
            }
        }
        p = p->next;
    }

    return -1;
}

static int alloc_fd(struct romfs_inode *h, uint32_t *fd)
{
    struct buf_desc *p = buf_desc_head, *r = NULL;
    int i;

    while (p) {
        for (i = 0; i < FD_BUF_SIZE; i++) {
            if (!p->fd_buf[i].h) {
                goto find;
            }
        }
        r = p;
        p = p->next;
    }

    p = malloc(sizeof(struct buf_desc));
    if (!p) {
        return -1;
    }

    memset(p, 0x0, sizeof(struct buf_desc));
    p->id = next_buf_id++ & 0xFFFF;
    i = 0;
    if (!r) {
        buf_desc_head = p;
    } else {
        r->next = p;
    }

find:
    p->fd_buf[i].h = h;
    p->used++;
    *fd = mk_fd(p->id, i);

    return 0;
}

static void free_fd_desc(struct buf_desc *p)
{
    struct buf_desc *r = NULL, *d = buf_desc_head;

    while (d) {
        if (d == p) {
            if (!r) { /* d is head */
                buf_desc_head = d->next;
            } else {
                r->next = d->next;
            }

            free(d);
            memset(d, 0x0, sizeof(struct buf_desc));
            return;
        }
        r = d;
        d = d->next;
    }

    return;
}

static void free_fd(uint32_t fd)
{
    struct buf_desc *buf_d;
    struct file_desc *file_d;
    int ret;

    ret = fd_to_desc(fd, &buf_d, &file_d);
    if (ret < 0) {
        return;
    }

    if (file_d->open_cnt > 0) {
        file_d->open_cnt--;
        if (file_d->open_cnt == 0) {
            memset(file_d, 0x0, sizeof(struct file_desc));
            if (buf_d->used > 0) {
                buf_d->used--;
            }
            if (buf_d->used == 0) {
                if ((buf_d != buf_desc_head) || buf_desc_head->next) {  /* do not free the last one */
                    free_fd_desc(buf_d);
                }
            }
        }
    }

    return;
}

static int find_fd(const char *file_name, uint32_t *fd)
{
    struct romfs_inode *h = NULL;
    int ret;

    /*Decrement all lifes. Make the entries older, refer to lv_img_cache.c */
    for(int i = 0; i< FILE_INODE_BUF_SIZE; i++) {
        if(file_inode_desc[i].life > INT32_MIN + LV_IMG_CACHE_AGING) {
            file_inode_desc[i].life -= LV_IMG_CACHE_AGING;
        }
    }

    /*Is the file inode cached?*/
    for(int i = 0; i< FILE_INODE_BUF_SIZE; i++) {
        if((0 == strcmp(file_inode_desc[i].fname, file_name)) && file_inode_desc[i].h) {
            //match
            h = file_inode_desc[i].h;
            file_inode_desc[i].life = file_inode_desc[i].time_to_open * LV_IMG_CACHE_LIFE_GAIN;
            if(file_inode_desc[i].life > LV_IMG_CACHE_LIFE_LIMIT) file_inode_desc[i].life = LV_IMG_CACHE_LIFE_LIMIT;
#if ROMFS_DEBUG_CACHE_EN
            //printf("[%s] find file_inode_desc, h=0x%x, file_name=%s\n", __func__, (unsigned int)h, file_name);
#endif
            break;
        }
    }

    /* The file inode is not cached then cache it now */
    if(!h) {
        /*The file inode desc is not cached then cache it now*/
        int cache_id = 0;
        /*Find an entry to reuse. Select the entry with the least life*/
        for(int i = 1; i < FILE_INODE_BUF_SIZE; i++) {
           if(file_inode_desc[i].life < file_inode_desc[cache_id].life) {
               cache_id = i;
           }
        }

        memcpy(file_inode_desc[cache_id].fname, file_name, sizeof(file_inode_desc[cache_id].fname));

        /*Open the image and measure the time to open*/
        uint32_t t_start = lv_tick_get();
        file_inode_desc[cache_id].time_to_open = 0;

        h = find_file(file_name, 1);
        if(!h) {
            file_inode_desc[cache_id].life = INT32_MIN; /*Make the empty entry very "weak" to force its use  */
            ret = -1;
        }

        file_inode_desc[cache_id].h = h;
        file_inode_desc[cache_id].life = 0;

        /*If `time_to_open` was not set in the open function set it here*/
        if(file_inode_desc[cache_id].time_to_open == 0) {
            file_inode_desc[cache_id].time_to_open = lv_tick_elaps(t_start);
        }
        if(file_inode_desc[cache_id].time_to_open == 0) file_inode_desc[cache_id].time_to_open = 1;

#if ROMFS_DEBUG_CACHE_EN
        printf("[%s] new file_inode_desc, h=0x%x, file_name=%s\n", __func__, (unsigned int)h, file_name);
#if 0
        //dump cache
        for(int i = 0; i< FILE_INODE_BUF_SIZE; i++) {
            printf("-- dump: file_inode_desc[%d].fname = %s, life = %d, time_to_open=%d\n", i, file_inode_desc[i].fname, file_inode_desc[i].life, (unsigned int)file_inode_desc[i].time_to_open);
        }
#endif
#endif
    }

    ret = get_file_fd(h, fd);
    if (ret < 0) {
        ret = alloc_fd(h, fd);
    }

    return ret;
}


static void romfs_lock(void)
{
    if (use_lock) {
#if USE_SDK_API
        uos_take_mutex(romfs_mutex);
#endif
    }
}

static void romfs_unlock(void)
{
    if (use_lock) {
#if USE_SDK_API
        uos_release_mutex(romfs_mutex);
#endif
    }
}

static bool lv_romfs_ready(lv_fs_drv_t *drv)
{
    return romfs_inited == 1;
}

static lv_fs_res_t lv_romfs_open(lv_fs_drv_t *drv, void *file_p, const char *fname, lv_fs_mode_t mode)
{
    struct file_desc *file_d;
    uint32_t fd;
    int ret;

    romfs_lock();

#if ROMFS_DEBUG_TIME_EN
    uint32_t tick_start = Hal_Timer_Get_Counter();
#endif

    if (mode != LV_FS_MODE_RD) {
        goto error;
    }

    ret = find_fd(fname, &fd);
    if (ret < 0) {
        goto error;
    }

    ret = fd_to_desc(fd, NULL, &file_d);
    if (ret < 0 || !file_d->h) {
        goto error;
    }

    file_d->open_cnt++;
    *(uint32_t *)file_p = fd;

#if ROMFS_DEBUG_EN
    printf("[%s] h=0x%x\n", __func__, (unsigned int)file_d->h);
    uint8_t *addr = get_file_data(file_d->h);
    printf("[%s]: mmap vaddr=0x%x\n", __func__, (unsigned int)addr);
    uint8_t *data = get_file_data(file_d->h) + file_d->rd_pos;
    printf("[%s]: read data=0x%x, rd_pos=0x%x\n", __func__, (unsigned int)data, (unsigned int)file_d->rd_pos);
#endif

#if ROMFS_DEBUG_TIME_EN
    // 1s = 32768 ticks;
    printf("[%s] tick = %d, fname = %s, file_p = 0x%x\n", __func__, (int)(Hal_Timer_Get_Counter() - tick_start), fname, (unsigned int)file_p);
#endif

    romfs_unlock();

    return LV_FS_RES_OK;
error:
    romfs_unlock();
    return LV_FS_RES_INV_PARAM;
}

static lv_fs_res_t lv_romfs_close(lv_fs_drv_t *drv, void *file_p)
{
    uint32_t fd = *(uint32_t *)file_p;

    romfs_lock();
    if (fd_is_invalid(fd)) {
        romfs_unlock();
        return LV_FS_RES_INV_PARAM;
    }

    free_fd(fd);
    romfs_unlock();
    return LV_FS_RES_OK;
}


static lv_fs_res_t lv_romfs_mmap(lv_fs_drv_t *drv, uint8_t **addr, uint32_t length, void *file_p, uint32_t offset)
{
    struct file_desc *file_d;
    int ret;
    uint32_t fd = *(uint32_t *)file_p;

    romfs_lock();

    ret = fd_to_desc(fd, NULL, &file_d);
    if ((ret < 0) || (file_d->open_cnt == 0) || !file_d->h || (offset >= romfs_get_inode_size(file_d->h))) {
        romfs_unlock();
        return LV_FS_RES_INV_PARAM;
    }

    if(romfs_is_xip) {
        *addr = get_file_data(file_d->h) + offset;
    } else {
        //printf("[%s] not support internal spi flash.\n", __func__);
        *addr = NULL;
        romfs_unlock();
        return LV_FS_RES_FS_ERR;
    }

    romfs_unlock();

    return LV_FS_RES_OK;
}

static lv_fs_res_t lv_romfs_read(lv_fs_drv_t *drv, void *file_p, void *buf, uint32_t btr, uint32_t *br)
{
    uint32_t fd, len, remain;
    uint8_t *data;
    int ret;
    struct file_desc *file_d;

    romfs_lock();

    *br = 0;
    fd = *(uint32_t *)file_p;
    ret = fd_to_desc(fd, NULL, &file_d);
    if ((ret < 0) || (file_d->open_cnt == 0) || !file_d->h) {
        romfs_unlock();
        return LV_FS_RES_INV_PARAM;
    }

    remain = romfs_get_inode_size(file_d->h) - file_d->rd_pos;
    len = (btr < remain)?btr:remain;

    if (len == 0) {
        romfs_unlock();
        return LV_FS_RES_OK;
    }

    data = get_file_data(file_d->h);
    if(data == NULL) {
        romfs_unlock();
        return LV_FS_RES_FS_ERR;
    }
    data += file_d->rd_pos;

    if(romfs_is_xip) {
        memcpy(buf, data, len);
        *br = len;
    } else {
#ifndef USE_LV_PCFS
        ret = partition_read((const char *)data, len, buf);
        if(ret < 0) {
          romfs_unlock();
          return LV_FS_RES_FS_ERR;

        }
        *br = (uint32_t)ret;
#else
        *br = 0;
#endif
    }

    file_d->rd_pos += *br;
    romfs_unlock();

    return LV_FS_RES_OK;
}

static lv_fs_res_t lv_romfs_seek(lv_fs_drv_t *drv, void *file_p, uint32_t pos)
{
    struct file_desc *file_d;
    int ret;
    uint32_t fd = *(uint32_t *)file_p;

    romfs_lock();

    ret = fd_to_desc(fd, NULL, &file_d);
    if ((ret < 0) || (file_d->open_cnt == 0)) {
        romfs_unlock();
        return LV_FS_RES_INV_PARAM;
    }

    if(pos > romfs_get_inode_size(file_d->h)) {
       pos = romfs_get_inode_size(file_d->h);
    }

    file_d->rd_pos = pos;

    romfs_unlock();

    return LV_FS_RES_OK;
}

static lv_fs_res_t lv_romfs_tell(struct _lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{
    uint32_t fd;
    int ret;
    struct file_desc *file_d;

    romfs_lock();

    fd = *(uint32_t *)file_p;
    ret = fd_to_desc(fd, NULL, &file_d);
    if ((ret < 0) || (file_d->open_cnt == 0) || !file_d->h) {
        romfs_unlock();
        return LV_FS_RES_INV_PARAM;
    }

    *pos_p = file_d->rd_pos;

    romfs_unlock();

    return LV_FS_RES_OK;
}

static lv_fs_res_t lv_romfs_size(lv_fs_drv_t *drv, void *file_p, uint32_t *size_p)
{
    uint32_t fd = *(uint32_t *)file_p;
    int ret;
    struct file_desc *file_d;

    romfs_lock();

    ret = fd_to_desc(fd, NULL, &file_d);
    if ((ret < 0) || (file_d->open_cnt == 0) || !file_d->h) {
        romfs_unlock();
        return LV_FS_RES_INV_PARAM;
    }
    *size_p = romfs_get_inode_size(file_d->h);

    romfs_unlock();

    return LV_FS_RES_OK;
}

#ifdef USE_LV_PCFS
#define FILENAME "resource.bin"

static int romfs_init_pc_simulator(void)
{
    void *p;
    char filename[LV_PCFS_MAX_FILE_NAME];

    snprintf(filename, LV_PCFS_MAX_FILE_NAME, "%s%s", pcfs_get_exec_path(), FILENAME);
    SDL_RWops *io = SDL_RWFromFile(filename, "rb");
    if(!io) {
        printf("[%s]open %s failed\n", __func__, filename);
        return -1;
    }

    size_t size = SDL_RWseek(io, 0, RW_SEEK_END);
    p = malloc(size);
    SDL_RWseek(io, 0, RW_SEEK_SET);
    SDL_RWread(io, p, 1, size);
    SDL_RWclose(io);

    grsb = p;

    return 0;
}
#endif

static void reset_file_inode_desc(void) {
    memset((void *)&file_inode_desc, 0, sizeof(file_inode_desc_t)*FILE_INODE_BUF_SIZE);
}

#ifndef USE_LV_PCFS
static void reset_inode_desc(void) {
    memset((void *)&inode_desc, 0, sizeof(inode_desc_t));
    inode_desc.inode = (struct romfs_inode *)romfs_inode;
    inode_desc.inode_size = sizeof(romfs_inode);
}
#endif

static void lv_romfs_deinit(void)
{
    struct buf_desc *p, *q;

#if USE_SDK_API
    if (romfs_mutex) {
        uos_free_mutex(romfs_mutex);
        romfs_mutex = INVALID_MUTEX_ID;
    }
#endif

    if (buf_desc_head) {
        p = buf_desc_head;
        while (p) {
            q = p->next;
            free(p);
            p = q;
        }
        buf_desc_head = NULL;
    }
#ifndef USE_LV_PCFS
    reset_inode_desc();
#endif
    reset_file_inode_desc();

    romfs_inited = 0;
}

#ifndef USE_LV_PCFS
static uint32_t get_resource_start_address(const char * pentry_name, uint32_t offset)
{
    if(!pentry_name) {
        /*eg: boot33img internal romfs */
        romfs_is_xip = true;
        return offset;
    }
    _ptentry * resource_begin_Entry = ptable_find_entry(pentry_name);

    if(partition_is_internal_flash(pentry_name)) {
        /* internal spi nor flash, eg 0x90000000, can not XIP*/
        romfs_is_xip = false;
    } else {
        /* external nor flash, eg 0x80000000, can XIP */
        romfs_is_xip = true;
    }

    return resource_begin_Entry->vstart + offset;
}
#endif

void lv_romfs_init(int lock_enable, const char * pentry_name, uint32_t offset)
{
    lv_fs_drv_t romfs_drv;

    if (romfs_inited) {
        lv_romfs_deinit();
    }

#if USE_SDK_API
    if (lock_enable) {
        romfs_mutex = uos_new_mutex();
        if (romfs_mutex == INVALID_MUTEX_ID) {
            printf("lv_romfs_init failed, create mutex error\n");
            return;
        }
        use_lock = 1;
    }
#endif

    if (drv_added == 0) {
        memset(&romfs_drv, 0, sizeof(lv_fs_drv_t));
        romfs_drv.file_size  = sizeof(uint32_t);
        romfs_drv.letter     = ROMFS_LETTER;
        romfs_drv.ready_cb      = lv_romfs_ready;
        romfs_drv.open_cb   = lv_romfs_open;
        romfs_drv.close_cb  = lv_romfs_close;
        romfs_drv.read_cb   = lv_romfs_read;
        romfs_drv.mmap_cb   = lv_romfs_mmap;
        romfs_drv.seek_cb   = lv_romfs_seek;
        romfs_drv.size_cb   = lv_romfs_size;
        romfs_drv.tell_cb   = lv_romfs_tell;
        lv_fs_drv_register(&romfs_drv);
        drv_added = 1;
    }

    romfs_inited = 1;
    romfs_is_xip = true;
#ifdef USE_LV_PCFS
    if (romfs_init_pc_simulator() < 0) {
        romfs_inited = 0;
    };
#else
    grsb = (void *)get_resource_start_address(pentry_name, offset);
    reset_inode_desc();
#endif

    reset_file_inode_desc();
}

#endif /* LV_USE_FILESYSTEM && USE_LV_ROMFS */
